package com.lemon.conf.redis.custom;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import org.springframework.util.MethodInvoker;

import com.lemon.conf.redis.RedisUtil;
import com.lemon.utils.MD5;
import com.lemon.utils.StringUtil;
import com.lemon.utils.sval.StaticValues;

/**
 * 手动刷新缓存实现类
 */
@Component
//@Service
public class CacheSupportImpl implements CacheSupport , InvocationRegistry {

	private static final Logger logger = LoggerFactory.getLogger(CacheSupportImpl.class);

	/**
	 * 记录容器与所有执行方法信息
	 */
	private Map<String, Set<CachedInvocation>> cacheToInvocationsMap;

	/**
	 * 当程序启动后,每个方法的第一次请求都会添加到这个map里面,
	 * 如果不存在这里 , 下面添加到刷新方法的map中就会重复添加某一个要执行的方法,
	 * 所以在每次添加要刷新检测的map的时候都会判断一下这个,如果当前方法已经存在了,则不需要再次注册为要刷新的方法
	 */
	private Map<String , String> methodAspetMap = new ConcurrentHashMap<String , String>();//用户记录刷新的方法, 防止添加要刷新的方法

	@Autowired
	private CacheManager cacheManager;


	/**
	 * <b>Description:<em><blockquote>
	 * 刷新缓存并重新获得最新的返回数据,然后修改缓存过期时间为注解中的时间设置.
	 * </blockquote></em></b>
	 * 
	 * @Title: refreshCache
	 * @param invocation
	 * @param cacheName
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	private void refreshCache( CachedInvocation invocation, String cacheName ) {

		boolean invocationSuccess;
		Object computed = null;
		try {
			computed = invoke(invocation);
			invocationSuccess = true;
		} catch (Exception ex) {
			invocationSuccess = false;
			ex.printStackTrace();
		}
		if (invocationSuccess) {
			if (cacheToInvocationsMap.get(cacheName.split("#")[0]) != null) {
				cacheManager.getCache(cacheName).put(invocation.getKey(), computed);
			}
		}
	}

	/**
	 * <b>Description:<em><blockquote>
	 * 执行当前注解所在的方法
	 * </blockquote></em></b>
	 * 
	 * @Title: invoke
	 * @param invocation
	 * @return
	 * @throws ClassNotFoundException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	private Object invoke( CachedInvocation invocation ) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
		final MethodInvoker invoker = new MethodInvoker();
		// Object bean = SpringContextUtil.getApplicationContext().getBean("commodityVipCouponServiceImpl");
		invoker.setTargetObject(invocation.getTargetBean());
		invoker.setArguments(invocation.getArguments());
		invoker.setTargetMethod(invocation.getTargetMethod().getName());
		invoker.prepare();
		return invoker.invoke();
	}

	/**
	 * <b>Description:<em><blockquote>
	 * 创建一个由cachename个数的set集合,存储要执行的缓存和方法
	 * </blockquote></em></b>
	 * 
	 * @Title: initialize
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	@PostConstruct
	public void initialize() {
		try {
			Collection<String> cacheNames = cacheManager.getCacheNames();
			int size = cacheNames.size();
			cacheToInvocationsMap = new ConcurrentHashMap<String, Set<CachedInvocation>>(size);
			for (final String cacheName : cacheManager.getCacheNames()) {
				cacheToInvocationsMap.put(cacheName, new CopyOnWriteArraySet<CachedInvocation>());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * <p>
	 * Title: registerInvocation
	 * </p>
	 * <p>
	 * Description:(一句话阐述): 通过反射获得的注解和通知获得的方法参数封装到一个缓存类里面CachedInvocation.java
	 * </p>
	 * 
	 * @param targetBean
	 * @param targetMethod
	 * @param arguments
	 * @param annotatedCacheNames
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	@Override
	public void registerInvocation( Object targetBean, Method targetMethod, Object [] arguments, Set<String> annotatedCacheNames, String keyName ) {

		//此处是通过反射获得参数然后通过自定义key生成key,为了和保存在redis里面key一致
		//这样在刷新缓存的时候就可以对上了
		//如果不是自定义key模式生成的 将用原生key模式将参数信息封装成key
		//默认key模式是将参数按顺序拼接起来,没有任何分隔符
		//所以建议在设置key的时候用自定义的, 如果参数较多的话可以用默认的
		//但是用key= 的时候里面最好不要加自定义的东西 , 这里还没有处理自己加的key 的信息
		String aspKey = "";  //用户记录刷新的方法, 防止添加要刷新的方法
		Object key = "";
		if ( keyName.equals("") ) {
			StringBuilder sb = new StringBuilder();
			for(Object obj : arguments) {
				sb.append(obj.toString());
				String objName = obj.getClass().getName();
				aspKey += objName + "-" + obj.toString();
			}
			key = sb.toString();
			//
			String metClass = targetMethod.getDeclaringClass().getName();
			String metName = targetMethod.getName();
			aspKey = MD5.getMD5(metClass + ":" + metName + ":" + aspKey);
		} else {
			key = new BaseCacheKeyGenerator().generate(targetBean, targetMethod, arguments);
			//
			aspKey = key.toString();
		}

		/**
		 * 这里可以优化key 的生成方案. 和注解上的可以对应上就可以了
		 */
		System.out.println("=====" + key);

		if (!methodAspetMap.containsKey(aspKey)) {
			final CachedInvocation invocation = new CachedInvocation(key, targetBean, targetMethod, arguments,
					targetMethod.getName());
			for (final String cacheName : annotatedCacheNames) {
				String[] cacheParams = cacheName.split(StaticValues.CACHE_SEPARATOR);  // 前面通过#分隔判断设置的过期时间和刷新时间
				String realCacheName = cacheParams[0];
				if (!cacheToInvocationsMap.containsKey(realCacheName)) {
					this.initialize();
				}
				cacheToInvocationsMap.get(realCacheName).add(invocation);
			}
			methodAspetMap.put(aspKey , "y");
		}


		//将所有要执行的cachename的map集合都保存到redis中, 防止服务器启动之后,此变量为空
		/*try {
			RedisUtil.set("listen_cache_map", cacheToInvocationsMap);
		} catch (Exception e) {
			//将错误信息输出到日志
			MyException.writeErr(e, "redis保存cache缓存信息失败.");
		}*/
	}

	@Override
	public void refreshCache( String cacheName ) {
		this.refreshCacheByKey(cacheName, null);
	}

	/**
	 * <p>
	 * Title: refreshCacheByKey
	 * </p>
	 * <p>
	 * Description:(一句话阐述): 刷新缓存
	 * </p>
	 * 
	 * @param cacheName 完整的cacheNames 就是cache注解中的value或者cachenames的值 , 为了更新缓存之后将时间刷新
	 * @param cacheKey cacheable注解中的key的值
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	@Override
	public void refreshCacheByKey( String cacheName, String cacheKey ) {
//		Map<String, List> map = (Map<String, List>) RedisUtil.get("listen_cache_map", Map.class);
//		if ( cacheToInvocationsMap.size() == 0 ) {
//			Map<String, List> map2 = (Map<String, List>) RedisUtil.get("listen_cache_map", Map.class);
//
//			System.out.println(map);
//		}
//
//		cacheToInvocationsMap = ctk(map);

		boolean flag = false;

		String [] val = cacheName.split("#");
		if (cacheToInvocationsMap.get(val[0]) != null) {
			for (final CachedInvocation invocation : cacheToInvocationsMap.get(val[0])) {
				if (!StringUtil.isEmpty(cacheKey) && invocation.getKey().toString().equals(cacheKey)) {
					flag = true;
					refreshCache(invocation, cacheName);
				}
			}
		} else {
			// TODO 搭建tomcat集群, 当数据同步, 当这个tomcat重启的时候,把其他tomcat 的map拿过来, 这样就不会造成map重启之后为空了
		}
		if ( !flag ) {
			logger.info("cache:{}的刷新时间失效,即刻删除cache重新缓存", val[0]);
			RedisUtil.del(val[0] + "::" + cacheKey);
		}
/*		if (cacheToInvocationsMap.get(cacheName) != null) {
			for (final CachedInvocation invocation : cacheToInvocationsMap.get(cacheName)) {
				if (!StringUtil.isEmpty(cacheKey) && invocation.getKey().toString().equals(cacheKey)) {
					refreshCache(invocation, cacheName);
				}
			}
		}*/	
	}

	/*private Map<String, Set<CachedInvocation>> ctk( Map<String, List> map ) {
		System.out.println(map);
		Map<String, Set<CachedInvocation>> m = new ConcurrentHashMap<String, Set<CachedInvocation>>();
		for(Entry<String, List> entry : map.entrySet()) {
			Set<CachedInvocation> result = new HashSet<CachedInvocation>();
			List list = entry.getValue();
			for(int i = 0; i < list.size(); i++) {
				System.out.println(list.get(i));
				JSONObject json = (JSONObject) list.get(i);
				// CachedInvocation cachedInvocation = JSONObject.parseObject(json.toJSONString(), CachedInvocation.class);
				// Method object = (Method) json.get("targetMethod");
				Object key = json.get("key");
				Object targetBean = json.getObject("targetBean", Object.class);
				JSONArray arguments = json.getJSONArray("arguments");
				for(int j = 0; j < arguments.size(); j++) {
					Object object2 = arguments.get(i);
					TkCommodityVipCoupon object = (TkCommodityVipCoupon) arguments.get(i);
					System.out.println(object);
				}
				Object targetMethodName = json.get("targetMethodName");
				CachedInvocation cachedInvocation = new CachedInvocation(key, targetBean, null, arguments.toArray(), targetMethodName.toString());

				result.add(cachedInvocation);
			}
			//			System.out.println(result);
			m.put(entry.getKey(), result);
		}

		//List<Student> list2=(List<Student>)JSONArray.toList(JSONArray.fromObject(arrayStr), Student.class);
		return m;
	}*/
}
